feat: Add ability to download specific tool versions used by hooks. TLDR: add --hook-config=--tool-version=1.2.3 for pinning wrapped tool version. Works for all tools (even terraform and tofu) except checkov - #1002
Conversation
Adds openspec/config.yaml plus the propose/explore/apply/update/ sync/archive skills and opsx slash commands, then registers the new skills in the AGENTS.md skill routing table so agents load them automatically. Assisted-by: Sisyphus:claude-sonnet-5 claude
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughHooks now support version-pinned tool resolution, cached installers, and resolved executable paths across Terraform, Terragrunt, and auxiliary tools. Documentation covers version pinning, Renovate configuration, cache persistence, and runtime installer usage. ChangesVersion-aware hook execution
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Hook as Hook
participant Common as common::per_dir_hook
participant Resolver as common::resolve_tool_path
participant Tool as Resolved tool
Hook->>Common: pass tool_name and HOOK_CONFIG
Common->>Resolver: resolve version and executable
Resolver-->>Common: return tool_path
Common->>Tool: execute per-directory or whole-repository command
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
b55dcfc to
e24e21b
Compare
yermulnik
left a comment
There was a problem hiding this comment.
I didn't read any of the Spec DMs — that (along with coding part of the PR) is too much of text for a single PR to be read be a human. Please split into separate PRs to ease review process and to separate entities. Thanks.
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>
Co-authored-by: Maksym Vlasov <MaxymVlasov@users.noreply.github.com>
.agents/commands/opsx/, .agents/skills/openspec-*/, and openspec/ are personal local tooling, not meant to be shared via this repo's history. They stay on disk, untracked, ignored via a per-repo excludesFile configured outside this repo (~/.gitconfig includeIf). Assisted-by: Sisyphus:claude-sonnet-5 opencode
There was a problem hiding this comment.
Pull request overview
This PR adds opt-in, per-hook tool version pinning via --hook-config=--tool-version=... for hooks that wrap downloadable binaries, resolving/caching pinned versions on demand by reusing the existing tools/install/<tool>.sh installers. It also updates docs to explain usage (including a Docker cache mount pattern) and adjusts hook plumbing so wrapped tools are invoked via a resolved tool_path.
Changes:
- Extend
common::per_dir_hookto resolve a wrapped tool binary path (optionally pinned + cached) and pass it into hook implementations. - Add
common::resolve_tool_path+ supporting helpers (--tool-version,--tool-version-mode, OS/arch detection) and wire affected hooks to use the resolved tool path. - Document the new pinning feature and add contributor guidance about the new runtime installer call site.
Reviewed changes
Copilot reviewed 21 out of 21 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
tools/install/_common.sh |
Make /.env sourcing conditional to allow installer scripts to run outside Docker builds. |
README.md |
Document --tool-version / --tool-version-mode, Renovate custom manager, and Docker cache mounting. |
hooks/_common.sh |
Add tool resolution + caching utilities and update common::per_dir_hook signature/behavior to pass a resolved tool_path. |
hooks/terraform_fmt.sh |
Route hook execution through resolved Terraform/OpenTofu binary path. |
hooks/terraform_validate.sh |
Route validate/init flow through resolved Terraform/OpenTofu binary path. |
hooks/terraform_providers_lock.sh |
Route providers lock through resolved Terraform/OpenTofu binary path. |
hooks/terraform_tflint.sh |
Resolve tflint path (incl. for --init) and pass into per-dir execution. |
hooks/terraform_docs.sh |
Resolve terraform-docs path (optionally pinned) before building the command. |
hooks/terraform_tfsec.sh |
Resolve tfsec path and pass into per-dir/whole-repo execution. |
hooks/terraform_trivy.sh |
Resolve trivy path and pass into per-dir/whole-repo execution. |
hooks/terrascan.sh |
Resolve terrascan path and pass into per-dir/whole-repo execution. |
hooks/tfupdate.sh |
Resolve tfupdate path and pass into per-dir/whole-repo execution. |
hooks/terragrunt_fmt.sh |
Resolve terragrunt path and pass into per-dir/whole-repo execution. |
hooks/terragrunt_validate.sh |
Resolve terragrunt path and pass into per-dir/whole-repo execution. |
hooks/terragrunt_validate_inputs.sh |
Resolve terragrunt path and pass into per-dir/whole-repo execution. |
hooks/terragrunt_providers_lock.sh |
Resolve terragrunt path and pass into per-dir/whole-repo execution. |
hooks/terraform_checkov.sh |
Adapt to new per_dir_hook signature while keeping checkov unpinned/no resolved binary. |
hooks/infracost_breakdown.sh |
Resolve infracost path (optionally pinned) before running breakdown. |
AGENTS.md |
Extend skill routing table with OpenSpec-related skills. |
.github/CONTRIBUTING.md |
Note that tools/install/<tool>.sh is now used at runtime for --tool-version pinning. |
.github/AI_POLICY.md |
Add a “Recommended tooling” section referencing OpenSpec workflow. |
Comments suppressed due to low confidence (2)
README.md:460
- This YAML example is not valid as written because args must be indented under the hook item. Update the indentation so users can copy/paste it.
- id: terraform_validate
args:
- --hook-config=--tf-path=opentofu
- --hook-config=--tool-version=1.12.0
```
README.md:481
- This YAML example is not valid as written because args must be indented under the hook item. The current indentation will fail YAML parsing if copied.
- id: terraform_tflint
args:
- --hook-config=--tool-version=0.50.0
- --hook-config=--tool-version-mode=prefer-local
```
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
hooks/_common.sh (1)
615-628: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winUnrecognized
--tool-version-modevalues fall through to strict silently.A typo (
prefer_local,preferlocal) is indistinguishable fromstrict, so users get a download instead of the local binary with no hint why. A small validation on the accepted set would surface config mistakes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hooks/_common.sh` around lines 615 - 628, Validate tool_version_mode in the configuration handling around common::get_hook_config_value and accept only the supported modes, including prefer-local and strict. For any unrecognized value, emit a clear configuration warning or error instead of silently proceeding through the strict download behavior; preserve the existing behavior for valid values.hooks/terragrunt_fmt.sh (1)
40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueTerragrunt hooks document
tool_pathas a Terraform/OpenTofu binary. Thetf_path→tool_pathrename carried the Terraform-specific wording into all four Terragrunt wrappers, which actually receive a resolvedterragruntbinary; each file'srun_hook_on_whole_repoblock already uses the correct generic wording.
hooks/terragrunt_fmt.sh#L40-L40: change the description to "resolved path to the wrapped tool's binary".hooks/terragrunt_providers_lock.sh#L42-L42: apply the same wording change.hooks/terragrunt_validate.sh#L42-L42: apply the same wording change.hooks/terragrunt_validate_inputs.sh#L42-L42: apply the same wording change.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hooks/terragrunt_fmt.sh` at line 40, Update the tool_path description in the run_hook_on_whole_repo blocks to say “resolved path to the wrapped tool's binary” instead of Terraform/OpenTofu-specific wording. Apply this change in hooks/terragrunt_fmt.sh (line 40), hooks/terragrunt_providers_lock.sh (line 42), hooks/terragrunt_validate.sh (line 42), and hooks/terragrunt_validate_inputs.sh (line 42).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@hooks/_common.sh`:
- Around line 333-340: Check the exit status of common::resolve_tool_path
immediately after the tool_path assignment in the hook flow, and return or exit
non-zero before invoking run_hook_on_whole_repo when resolution fails. Apply the
same propagation to the early tool-path resolution in terraform_tflint.sh, and
ensure resolve_tool_path consistently returns a non-zero status for every
failure path.
In `@hooks/terraform_docs.sh`:
- Around line 24-26: Update the argument-rewriting loop in terraform_docs.sh so
the --config value is prefixed with the repository path only when it is
relative. Preserve values beginning with / unchanged, while retaining the
existing conversion for relative --config paths.
In `@README.md`:
- Line 443: Update the version-pinning description near the wrapped-tool
download behavior to replace the GitHub-specific wording with “the tool’s
upstream release assets” or equivalent, while preserving the existing caching
and first-use download behavior.
- Around line 447-460: Correct the indentation of args in both YAML examples,
terraform_tflint and terraform_validate, so args is nested beneath its
corresponding - id entry and the examples form valid pre-commit configuration.
---
Nitpick comments:
In `@hooks/_common.sh`:
- Around line 615-628: Validate tool_version_mode in the configuration handling
around common::get_hook_config_value and accept only the supported modes,
including prefer-local and strict. For any unrecognized value, emit a clear
configuration warning or error instead of silently proceeding through the strict
download behavior; preserve the existing behavior for valid values.
In `@hooks/terragrunt_fmt.sh`:
- Line 40: Update the tool_path description in the run_hook_on_whole_repo blocks
to say “resolved path to the wrapped tool's binary” instead of
Terraform/OpenTofu-specific wording. Apply this change in
hooks/terragrunt_fmt.sh (line 40), hooks/terragrunt_providers_lock.sh (line 42),
hooks/terragrunt_validate.sh (line 42), and hooks/terragrunt_validate_inputs.sh
(line 42).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: bc8e303e-4ccc-4c41-bad1-d1e6a6a03c0a
📒 Files selected for processing (21)
.github/AI_POLICY.md.github/CONTRIBUTING.mdAGENTS.mdREADME.mdhooks/_common.shhooks/infracost_breakdown.shhooks/terraform_checkov.shhooks/terraform_docs.shhooks/terraform_fmt.shhooks/terraform_providers_lock.shhooks/terraform_tflint.shhooks/terraform_tfsec.shhooks/terraform_trivy.shhooks/terraform_validate.shhooks/terragrunt_fmt.shhooks/terragrunt_providers_lock.shhooks/terragrunt_validate.shhooks/terragrunt_validate_inputs.shhooks/terrascan.shhooks/tfupdate.shtools/install/_common.sh
|
|
||
| > All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook. | ||
|
|
||
| 1. You can pin a specific version of the wrapped tool per hook, independent of whatever is on your `$PATH` or baked into the Docker image. If that version isn't already cached locally, it's downloaded from the tool's GitHub releases on first use, then reused (without re-downloading) on every subsequent run. |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Avoid claiming every binary is downloaded from GitHub releases.
Terraform/OpenTofu resolution uses HashiCorp release installers, so this should say “the tool’s upstream release assets” (or similar) rather than specifically “GitHub releases.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` at line 443, Update the version-pinning description near the
wrapped-tool download behavior to replace the GitHub-specific wording with “the
tool’s upstream release assets” or equivalent, while preserving the existing
caching and first-use download behavior.
There was a problem hiding this comment.
I don't see an issue here. "Github release" is more undersandable, IMO.
@yermulnik ?
There was a problem hiding this comment.
"GitHub releases" describes the feature, but e.g. "GitHub releases page" (or with capitalized "Releases" to mark the section on the web-site) describes the actual location where we download assets from.
So I'd add "page" after "releases" to provide clarity. However, tech-savvies do indeed often say "Github releases" meaning "Github Releases page" =)
Up to you 👍
There was a problem hiding this comment.
Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!
`local -r tool_path=$(common::resolve_tool_path ...)` discarded the resolver's exit code, so a failed lookup (missing binary, bad --tool-version) left tool_path empty instead of aborting the hook. Split into declare/plain-assign-with-guard/readonly so the failure actually propagates. Applies to per_dir_hook, terraform_tflint, terraform_docs and infracost_breakdown. Also corrects stale tool_path doc comments (9 hooks, still described as "Terraform/OpenTofu binary" after the tf_path->tool_path refactor) and invalid YAML indentation in 3 README --tool-version examples. Assisted-by: Sisyphus:claude-sonnet-5 opencode
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
hooks/terragrunt_validate_inputs.sh (1)
25-28: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winUse the configured Terragrunt binary for version-gated subcommands.
common::terragrunt_version_ge_0.78checks theterragruntbinary in PATH, butterragrunt_validate_inputs.shselectsSUBCOMMAND/RUN_ALL_SUBCOMMANDbeforecommon::per_dir_hookresolves--tool-versionintotool_path. A pinned Terragrunt target crossing the0.78boundary can use the wrong CLI syntax; gate the subcommand selection against the configured/resolved tool version, or perform it wheretool_pathis available.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@hooks/terragrunt_validate_inputs.sh` around lines 25 - 28, Update terragrunt_validate_inputs.sh so SUBCOMMAND and RUN_ALL_SUBCOMMAND selection uses the configured or resolved Terragrunt binary represented by tool_path, rather than the PATH terragrunt checked by common::terragrunt_version_ge_0.78. Move the version-gated selection into the flow where tool_path is available, or pass that binary explicitly, preserving the correct CLI syntax across the 0.78 boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@hooks/terragrunt_validate_inputs.sh`:
- Around line 25-28: Update terragrunt_validate_inputs.sh so SUBCOMMAND and
RUN_ALL_SUBCOMMAND selection uses the configured or resolved Terragrunt binary
represented by tool_path, rather than the PATH terragrunt checked by
common::terragrunt_version_ge_0.78. Move the version-gated selection into the
flow where tool_path is available, or pass that binary explicitly, preserving
the correct CLI syntax across the 0.78 boundary.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 12a46dd1-a722-4db4-ae27-3d3c5d421e45
📒 Files selected for processing (13)
README.mdhooks/_common.shhooks/infracost_breakdown.shhooks/terraform_docs.shhooks/terraform_tflint.shhooks/terraform_tfsec.shhooks/terraform_trivy.shhooks/terragrunt_fmt.shhooks/terragrunt_providers_lock.shhooks/terragrunt_validate.shhooks/terragrunt_validate_inputs.shhooks/terrascan.shhooks/tfupdate.sh
🚧 Files skipped from review as they are similar to previous changes (12)
- hooks/infracost_breakdown.sh
- hooks/terraform_trivy.sh
- hooks/terraform_tflint.sh
- hooks/tfupdate.sh
- README.md
- hooks/terragrunt_validate.sh
- hooks/terraform_tfsec.sh
- hooks/terragrunt_fmt.sh
- hooks/terrascan.sh
- hooks/terraform_docs.sh
- hooks/terragrunt_providers_lock.sh
- hooks/_common.sh
Fix race condition during concurrent (pre-commit usually proceeds with 4 bunches simultaneously), which end in over9999 errors like this:
```bash
NOTE: The requested 'terraform' version '0.12.0' will be downloaded/used instead of whatever is on $PATH.
Downloading 'terraform' version '0.12.0'...
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 14.2M 100 14.2M 0 0 1333k 0 0:00:10 0:00:10 --:--:-- 1721k
unzip: cannot find or open terraform.zip, terraform.zip.zip or terraform.zip.ZIP.
ERROR: Failed to download 'terraform' version '0.12.0' via '/home/vm/.cache/pre-commit/repo29gptgn5/hooks/../tools/install/terraform.sh'.
```
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.
Suppressed comments (2)
README.md:442
- The introductory blockquote is grammatically awkward and slightly contradicts the section title (“Most hooks” vs “All hooks”). Rewording it to “Most hooks that wrap…” reads clearer and matches the heading.
> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.
README.md:485
- “as opposite to” is incorrect phrasing; it should be “as opposed to”.
4. By default, if a different version of the tool is already on `$PATH`, the pinned version still wins (with a warning message logged) based on `--hook-config=--tool-version-mode=strict`. Set it to `prefer-local` (as opposite to `strict`) to invert that: if the tool already resolves via `$PATH`, this local binary is used as-is and no download is attempted; the pinned version is only downloaded/used as a fallback when nothing is found locally.
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Suppressed comments (3)
hooks/_common.sh:596
- Specific-version resolution only searches the first, unpaginated
/releasesresponse intools/install/_common.sh:77, so pins older than GitHub's default 30-release page produce an empty URL and fail even though the release exists. For example, TFLintv0.44.1is currently release 31. Query the release/tag directly or paginate until the requested version is found; otherwise this feature cannot provide general historical pinning.
if ! (
cd "$tmp_dir" || exit 1
export "$env_var_name=$version"
"$installer_script" 1>&2
README.md:442
- This claim omits
terraform_wrapper_module_for_each, which wraps the downloadablehcleditrelease and has a matchingtools/install/hcledit.sh, but still calls barehcleditand errors when it is absent. Thus--tool-versionis not supported by all release-binary hooks as documented and as stated in the PR; wire that hook through the resolver or explicitly document it as another exclusion.
> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.
README.md:1394
- This example combines a non-root
USERIDwith a cache mounted below/root. The entrypoint switches UID viasu-execwithout changingHOME, and the resulting process cannot traverse/write/root, so pinned-tool resolution fails instead of using this cache. Mount a user-accessible path, setPCT_TOOL_CACHE_DIR, and create the host directory with the invoking user's ownership first.
-e "USERID=$(id -u):$(id -g)" \
-v ~/.cache/pre-commit-terraform:/root/.cache/pre-commit-terraform \
-v $(pwd):/lint -w /lint \
Copilot flagged several bugs in the --tool-version implementation
(review #4992979471):
- prefer-local mode probed "opentofu" on $PATH; the real binary is
"tofu", so the pin always won even when local should be preferred
- the final Docker image had no curl, so cache-miss downloads failed
in the published image
- terrascan's install script used bash4-only ${TARGETOS^}, breaking
under macOS's default bash 3.2
- the cache key omitted OS/arch, so a cache dir shared across
platforms (e.g. a Docker volume mount) could serve an incompatible
binary as a false hit
- specific-version resolution only searched GitHub's unpaginated,
30-release page, so pinning anything older silently failed
Also wires hcledit through the same resolver for
terraform_wrapper_module_for_each - the one release-binary hook
--tool-version didn't cover yet - and fixes the Docker cache-mount
README example, which mounted under /root while also dropping to a
non-root USERID.
Assisted-by: Sisyphus:claude-sonnet-5 opencode
|
Generated by Sisyphus:claude-sonnet-5 opencode Also addressed the 3 additional findings Copilot suppressed (lower-confidence, listed in the review body but never posted as separate review comments, so there's no thread to resolve here):
|
`ARG GITHUB_TOKEN` and `ENV INFRACOST_API_KEY` both have empty defaults; the real values are supplied externally (--build-arg / -e), never hardcoded. DL3064 can't tell that from the variable name alone, so it flags both as "potentially sensitive data". Assisted-by: Sisyphus:claude-sonnet-5 opencode
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.
Suppressed comments (3)
hooks/infracost_breakdown.sh:43
--tool-versionis still present inhook_config, and this function later treats every semicolon-delimited entry as an Infracostjqcheck. A pin such as--tool-version=0.65.0is therefore parsed as an expression and makes the hook fail after running Infracost. Filter the resolver-only keys before evaluating the cost checks.
local -r tool_version=$(common::get_hook_config_value "--tool-version")
local tool_path
tool_path=$(common::resolve_tool_path "$tool_name" "$tool_version") || exit $?
tools/install/_common.sh:83
- These new paginated requests use
CURL_CMD, but whenGITHUB_TOKENis set that command sends the literal headerAuthorization: ******(line 70), not the token. GitHub rejects it as bad credentials, so the newly exported CI token—and any user's token—causes cache-miss resolution to fail rather than raising the rate limit. Build the authorization header fromGITHUB_TOKENinstead of the placeholder.
page_releases=$("${CURL_CMD[@]}" -s "${RELEASES}?per_page=100&page=${page}")
tests/pytest/tool_version_test.py:889
- The generic wiring case cannot cover Infracost's new pinning path because the test intentionally ignores the hook result and its echo-only stub is not valid Infracost JSON. Add a dedicated test with valid API
0.2JSON plus--tool-version/a real cost check, so resolver settings being misinterpreted asjqexpressions is caught.
'infracost_breakdown.sh',
_CachedTool('infracost', 'infracost'),
(),
),
id='infracost',
tools/install/_common.sh calls api.github.com unauthenticated to resolve each tool's latest-release asset URL. build-image.yaml and build-image-test.yaml passed GITHUB_TOKEN via buildx `secrets:`, but no Dockerfile RUN mounts that secret - dead config since #496, never reconnected when #947 added ARG-based auth to the Dockerfile. Result: every docker buildx build (PR test, nightly, release) hits ~10 unauthenticated GH API calls x up to 2 platforms. GitHub's 60/hr unauthenticated limit is shared across all Actions runners, so any call landing on the limit gets no asset match -> curl gets an empty URL -> exit 3. Observed here as tflint, but any GH-hosted tool install can trip it. Same class of bug as 65fe9e4 (reusable-tox.yml), different surface. Assisted-by: Sisyphus:claude-sonnet-5 opencode
# [1.109.0](v1.108.1...v1.109.0) (2026-08-21) ### Features * Add ability to download specific tool versions used by hooks. TLDR: add `--hook-config=--tool-version=1.2.3` for pinning wrapped tool version. Works for all tools (even `terraform` and `tofu`) except `checkov` ([#1002](#1002)) ([f707bc5](f707bc5))
|
This PR is included in version 1.109.0 🎉 |
Description of your changes
What
This section was generated by AI.
--hook-config=--tool-version=X.Y.Zversion pinning to every hook that wraps a downloadable release binary (terraform_tflint,terraform_docs,terraform_tfsec/terraform_trivy,terrascan,infracost_breakdown,tfupdate,terragrunt_*, and Terraform/OpenTofu viaterraform_validate/terraform_fmt/terraform_providers_lock), resolved and cached on demand by invoking the existingtools/install/<tool>.shinstaller scripts - opt-in, zero effect on hooks that don't set it.--hook-config=--tool-version-mode=strict|prefer-localto control whether a pinned version always wins (strict, default) or an already-on-$PATHbinary is preferred when present (prefer-local).terraform_validate/terraform_fmt/terraform_providers_lock,--tf-pathcan now also be set to the literal valueterraform,opentofu, ortofu(instead of an actual binary path) when combined with--tool-version, to explicitly pick which of the two to pin rather than relying on$PATH-based auto-detection.--hook-config=--tool-version=) when a hook's tool is neither pinned nor found on$PATH, instead of deferring to a generic "command not found" failure later.checkov/terraform_checkovis explicitly excluded (pip-distributed, not a GitHub-release binary - forcing it through this path would fight its actual, already-working distribution model).README.mdsection documenting usage, a cache-directory-mounting note for persisting across Docker container runs, and a RenovatecustomManagersrecipe for keeping pins auto-bumped..github/CONTRIBUTING.md's "Add new hook" checklist to mention the new runtime call site alongside the existing Docker build-time one.tools/install/_common.sh:source /.envis now conditional on the file existing, so the installer scripts also run correctly when invoked directly at hook run-time (not just inside a Docker build).common::resolve_tool_path) was silently swallowed bylocal -r tool_path=$(...), lettingterraform_tflint,terraform_docs,infracost_breakdown, and the sharedper_dir_hookcontinue with an empty tool path instead of aborting with the resolver's own error message.tool_pathdoc comments left over from thetf_path->tool_pathrename in 9 hooks, and fixes invalid YAML indentation in 3--tool-versionREADME examples.terragrunt_validate/terragrunt_validate_inputs/terragrunt_fmt/terragrunt_providers_lockpicking their pre/post-0.78 CLI syntax off whateverterragruntis on$PATHinstead of the resolved/pinnedtool_pathbinary that actually runs - a pinned version on the other side of the 0.78 boundary from$PATHpicked the wrong syntax and broke.prefer-localmode probedopentofuon$PATHinstead of the realtofubinary so the pin always won regardless of mode; the published Docker image had nocurl, breaking cache-miss downloads;terrascan's installer used bash4-only${TARGETOS^}, breaking under macOS's bash 3.2; the cache key omitted OS/arch, so a cache dir shared across platforms (e.g. a Docker volume mount) could serve an incompatible binary as a false hit; and specific-version resolution only searched GitHub's unpaginated, 30-release page, so pinning anything older silently failed.hcleditthrough the same resolver forterraform_wrapper_module_for_each- the last release-binary hook--tool-versiondidn't yet cover./rootwhile also dropping to a non-rootUSERID(unreachable, permission denied).hadolintDL3064 false positive on the pre-existingARG GITHUB_TOKEN/ENV INFRACOST_API_KEY(both empty-default placeholders, real values supplied externally, never hardcoded).buildxbuilds failing with e.g.tflint.sh ... exit code: 3:build-image.yaml/build-image-test.yamlpassedGITHUB_TOKENvia buildxsecrets:, which nothing in the Dockerfile mounts - dead config left over from before--build-arg-based GitHub API auth was added, so every image build resolved tool versions via unauthenticated (60/hr, shared across all Actions runners) GitHub API calls. Now passed as aGITHUB_TOKENbuild-arg, matching whattools/install/_common.shalready expects.Why
This enables multi-version testing and the ability to pin the same versions of underlying tools by users, which is a decent improvement towards reproducibility.
Fix: #188, #270, #1009
Related: #722
How can we test changes
This section was generated by AI.
Run
pre-commit run terraform_tflint- first run downloads and caches tflint0.50.0into$XDG_CACHE_HOME/pre-commit-terraform(or$HOME/.cache/pre-commit-terraform), subsequent runs hit the cache with no network call. Same pattern works forterraform_validate/terraform_fmt/terraform_providers_lock(pins Terraform/OpenTofu), optionally combined with--hook-config=--tf-path=opentofuto force OpenTofu specifically regardless of what's on$PATH.Assisted-by
Specific models used per commit are specified in the commit messages.